package job

import (
	"bbs-web/internal/service"
	"context"
	rlock "github.com/gotomicro/redis-lock" // 分布式锁的实现
	"sync"
	"time"
)

type RankingJob struct {
	svc     service.RankingService
	timeout time.Duration // 一次运行的时间
	client  *rlock.Client
	rKey    string
	lock    *rlock.Lock
	//本地使用互斥锁，解决并发问题
	localLock *sync.Mutex
}

func NewRankingJob(svc service.RankingService, timeout time.Duration, client *rlock.Client) *RankingJob {
	return &RankingJob{svc: svc, timeout: timeout, client: client, rKey: "rlock:cron_job:ranking",
		localLock: &sync.Mutex{}}
}

func (r *RankingJob) Name() string {
	return "ranking"
}

func (r *RankingJob) Run() error {
	//localLock使用互斥锁，确定后续操作在同一时刻只有一个协程执行的，从而保证了对共享资源的安全访问。
	r.localLock.Lock()
	defer r.localLock.Unlock()
	if r.lock == nil {
		//没有抢到分布式锁，需要去抢锁
		ctx, cancle := context.WithTimeout(context.Background(), time.Second)
		defer cancle()
		lock, err := r.client.Lock(ctx, r.rKey, r.timeout, &rlock.FixIntervalRetry{
			Interval: time.Millisecond * 100,
			Max:      3,
		}, time.Second)
		if err != nil {
			// 重试多次无果，估计是别人抢到了锁，那我就不干了，等下次有机会再干
			return err
		}
		//	抢锁成功
		r.lock = lock
		//如何保证一直持有换个锁？持有的锁不会过期导致其他进程或者协程也能够获取到这个锁，从而避免并发问题或者竞争条件的发生。
		//使用协程一直刷新，每个协程要加上互斥锁
		go func() {
			r.localLock.Lock()
			defer r.localLock.Unlock()
			err2 := lock.AutoRefresh(r.timeout/2, time.Second)
			if err2 != nil {
				//锁被抢掉了or超时了,// 这里说明退出了续约机制 续约失败了怎么办？？ 不管他
				r.lock = nil
				//ctx1, cancel1 := context.WithTimeout(context.Background(), time.Second)
				//defer cancel1()
			}
		}()
	}

	ctx, cancle := context.WithTimeout(context.Background(), r.timeout)
	defer cancle()
	//lock, err := r.client.Lock(ctx, r.rKey, r.timeout+time.Second*60, &rlock.FixIntervalRetry{
	//	Interval: time.Millisecond * 100,
	//	Max:      3,
	//}, time.Second) // 过期时间要比redis的锁时间要久一点
	//if err != nil {
	//	return err
	//}
	//defer func() {
	//	ctx, cancel = context.WithTimeout(context.Background(), time.Minute)
	//	defer cancel()
	//	err1 := lock.Unlock(ctx)
	//	if err1 != nil {
	//		// 记录日志咯 分布式锁释放失败 也可以选择重试几次
	//
	//	}
	//}()
	return r.svc.TopN(ctx)

}

func (r *RankingJob) Close() error {
	r.localLock.Lock()
	//分配给一个局部变量 lock 的目的是为了在解锁本地互斥锁之后仍然能够访问分布式锁，并且安全地执行解锁操作。
	//如果直接在释放本地互斥锁后立即调用 r.lock.Unlock(ctx)，由于 r.lock 已经被置为 nil，会导致无法访问到原始的分布式锁对象，
	//从而无法正确地执行解锁操作。因此，我们需要在释放本地互斥锁之前将 r.lock 赋值给 lock 变量，以确保在后续能够安全地执行分布式锁的解锁操作。
	lock := r.lock
	r.lock = nil         // 这一句很重要！！！！释放分布式锁
	r.localLock.Unlock() //释放互斥锁
	//添加超时控制
	ctx, cancle := context.WithTimeout(context.Background(), time.Second)
	defer cancle()
	//释放这个局部变量
	return lock.Unlock(ctx)
}
